/*
 *  constant_info.c
 *
 *  Copyright (C) 2011  Jason<linux8192@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <assert.h>
#include <stdio.h>
#include "debug.h"
#include "type.h"
#include "constant_info.h"

/*
 * Constant info getters
 *
 * return the constant info length in class file (NOT sizeof(struct) !!!)
 */

static int parse_constant_utf8(cp_info_t **cp, const char *cp_buff)
{
	int bytes_length = read_u2(cp_buff + 1);
	int size = sizeof(cp_utf8_info_t) + bytes_length;
	cp_utf8_info_t *cp_utf8;

	LOG_TRACE();

	//dump_n_bytes(cp_buff, 32);
	LOGV("cp_utf8 length = %d(0x%02x)\n", read_u2(cp_buff + 1), read_u2(cp_buff + 1));

	cp_utf8 = *cp = malloc(size);

	if (!*cp) {
		LOGE("Allocate constant utf8 failed !\n");
		return 0;
	}

	bzero((void *)cp_utf8, size);

	cp_utf8->base.tag = CONSTANT_UTF8;
	cp_utf8->length = read_u2(cp_buff + 1);
	memcpy(cp_utf8->bytes, cp_buff + 3, cp_utf8->length);

	return __get_constant_info_length(CONSTANT_UTF8) + bytes_length;
}

static int parse_constant_integer(cp_info_t **cp, const char* cp_buff)
{
	int size = sizeof(cp_integer_info_t);
	cp_integer_info_t *cp_integer;

	LOG_TRACE();

	cp_integer = *cp = malloc(size);

	if (!*cp) {
		LOGE("Allocate constant integer failed !\n");
		return 0;
	}

	bzero((void *)cp_integer, size);

	cp_integer->base.tag = CONSTANT_INTEGER;
	cp_integer->bytes = read_u4(cp_buff + 1);

	return __get_constant_info_length(CONSTANT_INTEGER);
}

static int parse_constant_float(cp_info_t **cp, const char* cp_buff)
{
	int size = sizeof(cp_float_info_t);
	cp_float_info_t *cp_float;

	LOG_TRACE();

	cp_float = *cp = malloc(size);

	if (!*cp) {
		LOGE("Allocate constant float failed !\n");
		return 0;
	}

	bzero((void *)cp_float, size);

	cp_float->base.tag = CONSTANT_FLOAT;
	cp_float->bytes = read_u4(cp_buff + 1);

	return __get_constant_info_length(CONSTANT_FLOAT);
}

static int parse_constant_long(cp_info_t **cp, const char* cp_buff)
{
	int size = sizeof(cp_long_info_t);
	cp_long_info_t *cp_long;

	LOG_TRACE();

	cp_long = *cp = malloc(size);

	if (!*cp) {
		LOGE("Allocate constant long failed !\n");
		return 0;
	}

	bzero((void *)cp_long, size);

	cp_long->base.tag = CONSTANT_LONG;
	cp_long->bytes = read_u8(cp_buff + 1);

	return __get_constant_info_length(CONSTANT_LONG);
}

static int parse_constant_double(cp_info_t **cp, const char* cp_buff)
{
	int size = sizeof(cp_double_info_t);
	cp_double_info_t *cp_double;

	LOG_TRACE();

	cp_double = *cp = malloc(size);

	if (!*cp) {
		LOGE("Allocate constant double failed !\n");
		return 0;
	}

	bzero((void *)cp_double, size);

	cp_double->base.tag = CONSTANT_DOUBLE;
	cp_double->bytes = read_u8(cp_buff + 1);

	return __get_constant_info_length(CONSTANT_DOUBLE);
}

static int parse_constant_class(cp_info_t **cp, const char* cp_buff)
{
	int size = sizeof(cp_class_info_t);
	cp_class_info_t *cp_class;

	LOG_TRACE();

	cp_class = *cp = malloc(size);

	if (!*cp) {
		LOGE("Allocate constant class failed !\n");
		return 0;
	}

	bzero((void *)cp_class, size);

	cp_class->base.tag = CONSTANT_CLASS;
	cp_class->name_index = read_u2(cp_buff + 1);

	return __get_constant_info_length(CONSTANT_CLASS);
}

static int parse_constant_string(cp_info_t **cp, const char* cp_buff)
{
	int size = sizeof(cp_string_info_t);
	cp_string_info_t *cp_string;

	LOG_TRACE();

	cp_string = *cp = malloc(size);

	if (!*cp) {
		LOGE("Allocate constant string failed !\n");
		return 0;
	}

	bzero((void *)cp_string, size);

	cp_string->base.tag = CONSTANT_STRING;
	cp_string->string_index = read_u2(cp_buff + 1);

	return __get_constant_info_length(CONSTANT_STRING);
}

static int parse_constant_field_ref(cp_info_t **cp, const char* cp_buff)
{
	int size = sizeof(cp_field_ref_info_t);
	cp_field_ref_info_t *cp_field_ref;

	LOG_TRACE();

	cp_field_ref = *cp = malloc(size);

	if (!*cp) {
		LOGE("Allocate constant field_ref failed !\n");
		return 0;
	}

	bzero((void *)cp_field_ref, size);

	cp_field_ref->base.tag = CONSTANT_FIELD_REF;
	cp_field_ref->class_index = read_u2(cp_buff + 1);
	cp_field_ref->name_and_type_index = read_u2(cp_buff + 3);

	return __get_constant_info_length(CONSTANT_FIELD_REF);
}

static int parse_constant_method_ref(cp_info_t **cp, const char* cp_buff)
{
	int size = sizeof(cp_method_ref_info_t);
	cp_method_ref_info_t *cp_method_ref;

	LOG_TRACE();

	cp_method_ref = *cp = malloc(size);

	if (!*cp) {
		LOGE("Allocate constant method_ref failed !\n");
		return 0;
	}

	bzero((void *)cp_method_ref, size);

	cp_method_ref->base.tag = CONSTANT_METHOD_REF;
	cp_method_ref->class_index = read_u2(cp_buff + 1);
	cp_method_ref->name_and_type_index = read_u2(cp_buff + 3);

	return __get_constant_info_length(CONSTANT_METHOD_REF);
}

static int parse_constant_interface_method_ref(cp_info_t **cp, const char* cp_buff)
{
	int size = sizeof(cp_interface_method_ref_info_t);
	cp_interface_method_ref_info_t *cp_interface_method_ref;

	LOG_TRACE();

	cp_interface_method_ref = *cp = malloc(size);

	if (!*cp) {
		LOGE("Allocate constant interface_method_ref failed !\n");
		return 0;
	}

	bzero((void *)cp_interface_method_ref, size);

	cp_interface_method_ref->base.tag = CONSTANT_INTERFACE_METHOD_REF;
	cp_interface_method_ref->class_index = read_u2(cp_buff + 1);
	cp_interface_method_ref->name_and_type_index = read_u2(cp_buff + 3);

	return __get_constant_info_length(CONSTANT_INTERFACE_METHOD_REF);
}

static int parse_constant_name_and_type(cp_info_t **cp, const char* cp_buff)
{
	int size = sizeof(cp_name_and_type_info_t);
	cp_name_and_type_info_t *cp_name_and_type;

	LOG_TRACE();

	cp_name_and_type = *cp = malloc(size);

	if (!*cp) {
		LOGE("Allocate constant name_and_type failed !\n");
		return 0;
	}

	bzero((void *)cp_name_and_type, size);

	cp_name_and_type->base.tag = CONSTANT_NAME_AND_TYPE;
	cp_name_and_type->name_index = read_u2(cp_buff + 1);
	cp_name_and_type->descriptor_index = read_u2(cp_buff + 3);

	return __get_constant_info_length(CONSTANT_NAME_AND_TYPE);
}

int release_constant_poll(cp_info_t **list, const int cp_count)
{
	int i;
	for (i = 0; i < cp_count; ++i) {
		free(list[i]);
	}

	return 0;
}

typedef int (*PARSE_CONSTANT_INFO_FUNC)(cp_info_t **, const char *);

static PARSE_CONSTANT_INFO_FUNC get_constant_parser(int tag)
{
	switch (tag) {
	case CONSTANT_UTF8:			return parse_constant_utf8;
	case CONSTANT_INTEGER:			return parse_constant_integer;
	case CONSTANT_FLOAT:			return parse_constant_float;
	case CONSTANT_LONG:			return parse_constant_long;
	case CONSTANT_DOUBLE:			return parse_constant_double;
	case CONSTANT_CLASS:			return parse_constant_class;
	case CONSTANT_STRING:			return parse_constant_string;
	case CONSTANT_FIELD_REF:		return parse_constant_field_ref;
	case CONSTANT_METHOD_REF:		return parse_constant_method_ref;
	case CONSTANT_INTERFACE_METHOD_REF:	return parse_constant_interface_method_ref;
	case CONSTANT_NAME_AND_TYPE:		return parse_constant_name_and_type;

	default:
		return NULL;
	}
}

int parse_constant_pool(const char *cp_buff, cp_info_t **cp_list, const int cp_count)
{
	int	i;
	char	*cur = cp_buff;
	u1	tmp_tag;

	PARSE_CONSTANT_INFO_FUNC get_constant_info;

	LOG_ENTER();

	//dump_n_bytes(cp_buff, 128);

	LOGD("constant pool count = %d\n", cp_count);

	/*
	 * Notice the cp_count here is equal to the reality number of
	 * entries in the constant pool table.
	 */
	for (i = 0; i < cp_count; ++i) {
		LOGD("[CONST PARSER] %s: %d, i = %d, cur = 0x%016x\n", __FUNCTION__, __LINE__, i, cur);
		tmp_tag = read_u1(cur);
		if (!(get_constant_info = get_constant_parser(tmp_tag))) {
			LOGE("ERROR: unkown constant info tag = %d\n", tmp_tag);
			return 0;
		}

		cur += get_constant_info(&cp_list[i], cur);
		LOGD("[CONST PARSER] %s: %d, i = %d, cur = 0x%016x\n", __FUNCTION__, __LINE__, i, cur);
	}

	LOG_LEAVE();

	return (cur - cp_buff);
}

cp_info_t *get_constant_info_by_index(cp_info_t **cp_list, int index)
{
	/*
	 * constant reference index begin with 1, C array index begin 0
	 */
	//LOGD("%s:%d, index = %d\n", __FUNCTION__, __LINE__, index);
	return cp_list[index - 1];
}

inline char *get_utf8_string_by_cp_index(cp_info_t **cp_list, int cp_index)
{
	get_utf8_string_from_cp(get_constant_info_by_index(cp_list, cp_index));
}

inline char *get_utf8_string_from_cp(const cp_utf8_info_t *cp_utf8)
{
	assert(cp_utf8);
	LOGD("%s:%d, tag = %d\n", __FUNCTION__, __LINE__, cp_utf8->base.tag);
	assert(cp_utf8->base.tag == CONSTANT_UTF8);

	return cp_utf8->bytes;
}

void copy_utf8_string_from_cp(char **buf, const cp_utf8_info_t *cp_utf8)
{
	assert(buf);
	assert(cp_utf8);
	assert(cp_utf8->base.tag == CONSTANT_UTF8);

	memcpy(*buf, cp_utf8->bytes, cp_utf8->length);
	(*buf)[cp_utf8->length] = 0;
}

void copy_class_name_from_cp(char **buf, const cp_info_t **cp_list, int class_const_index)
{
	cp_class_info_t *cp_class;
	cp_utf8_info_t *cp_utf8;

	assert(buf);
	assert(cp_list);
	assert(class_const_index > 0);

	cp_class = get_constant_info_by_index(cp_list, class_const_index);

	assert(cp_class->base.tag == CONSTANT_CLASS);

	cp_utf8 = get_constant_info_by_index(cp_list, cp_class->name_index);

	assert(cp_utf8->base.tag == CONSTANT_UTF8);

	copy_utf8_string_from_cp(buf, cp_utf8);
}

static char *const_tag_string[] = {
	"NULL", "CONSTANT_UTF8", "NULL", "CONSTANT_INTEGER", "CONSTANT_FLOAT",
	"CONSTANT_LONG", "CONSTANT_DOUBLE", "CONSTANT_CLASS", "CONSTANT_STRING",
	"CONSTANT_FIELD_REF", "CONSTANT_METHOD_REF", "CONSTANT_INTERFACE_METHOD_REF", 
	"CONSTANT_NAME_AND_TYPE"
};

void dump_constant_utf8(cp_info_t *cp_info)
{
	cp_utf8_info_t *cp_utf8 = cp_info;
	int i;

	printf("%s:", __FUNCTION__);
	printf("\tlength = %d", cp_utf8->length);
	printf("\tbytes = ");

	for (i = 0; i < cp_utf8->length; ++i) {
		printf("%c", cp_utf8->bytes[i]);
	}

	printf("\n");
}

void dump_constant_integer(cp_info_t *cp_info)
{
	cp_integer_info_t *cp_integer = cp_info;

	printf("%s:", __FUNCTION__);
	printf("\tinteger value = %d\n", cp_integer->bytes);
}

void dump_constant_float(cp_info_t *cp_info)
{
	cp_float_info_t *cp_float = cp_info;

	printf("%s:", __FUNCTION__);
	printf("\tfloat value = %f\n", cp_float->float_value);
}

void dump_constant_long(cp_info_t *cp_info)
{
	cp_long_info_t *cp_long = cp_info;

	printf("%s:", __FUNCTION__);
	printf("\tlong value = %lu\n", cp_long->bytes);
}

void dump_constant_double(cp_info_t *cp_info)
{
	cp_double_info_t *cp_double = cp_info;

	printf("%s:", __FUNCTION__);
	printf("\tdouble value = %lf\n", cp_double->double_value);
}

void dump_constant_class(cp_info_t *cp_info)
{
	cp_class_info_t *cp_class = cp_info;

	printf("%s:", __FUNCTION__);
	printf("\tname index = %u\n", cp_class->name_index);
}

void dump_constant_string(cp_info_t *cp_info)
{
	cp_string_info_t *cp_string = cp_info;

	printf("%s:", __FUNCTION__);
	printf("\tstring = %u\n", cp_string->string_index);
}

void dump_constant_field_ref(cp_info_t *cp_info)
{
	cp_field_ref_info_t *cp_field_ref = cp_info;

	printf("%s:", __FUNCTION__);
	printf("\tclass index = %u", cp_field_ref->class_index);
	printf("\tname and type index = %u\n", cp_field_ref->name_and_type_index);
}

void dump_constant_method_ref(cp_info_t *cp_info)
{
	cp_method_ref_info_t *cp_method_ref = cp_info;

	printf("%s:", __FUNCTION__);
	printf("\tclass index = %u", cp_method_ref->class_index);
	printf("\tname and type index = %u\n", cp_method_ref->name_and_type_index);
}

void dump_constant_interface_method_ref(cp_info_t *cp_info)
{
	cp_interface_method_ref_info_t *cp_interface_method_ref = cp_info;

	printf("%s:", __FUNCTION__);
	printf("\tclass index = %u", cp_interface_method_ref->class_index);
	printf("\tname and type index = %u\n", cp_interface_method_ref->name_and_type_index);
}

void dump_constant_name_and_type(cp_info_t *cp_info)
{
	cp_name_and_type_info_t *cp_name_and_type = cp_info;

	printf("%s:", __FUNCTION__);
	printf("\tname index = %u", cp_name_and_type->name_index);
	printf("\tdescriptor index = %u\n", cp_name_and_type->descriptor_index);
}

typedef void (*DUMP_CONSTANT_INFO_FUNC)(cp_info_t *);

static DUMP_CONSTANT_INFO_FUNC get_constant_dumper(int tag)
{
	switch (tag) {
	case CONSTANT_UTF8:			return dump_constant_utf8;
	case CONSTANT_INTEGER:			return dump_constant_integer;
	case CONSTANT_FLOAT:			return dump_constant_float;
	case CONSTANT_LONG:			return dump_constant_long;
	case CONSTANT_DOUBLE:			return dump_constant_double;
	case CONSTANT_CLASS:			return dump_constant_class;
	case CONSTANT_STRING:			return dump_constant_string;
	case CONSTANT_FIELD_REF:		return dump_constant_field_ref;
	case CONSTANT_METHOD_REF:		return dump_constant_method_ref;
	case CONSTANT_INTERFACE_METHOD_REF:	return dump_constant_interface_method_ref;
	case CONSTANT_NAME_AND_TYPE:		return dump_constant_name_and_type;

	default:
		return NULL;
	}
}

void dump_constant_pool(cp_info_t **cp_list, const int cp_count)
{
	DUMP_CONSTANT_INFO_FUNC	dump_constant_info;
	int	i;

	LOG_ENTER();

	printf("constant pool dump:\n");
	for (i = 0; i < cp_count - 1; ++i) {
		printf("[%02d] tag = %s\n", i, const_tag_string[cp_list[i]->tag]);
		dump_constant_info = get_constant_dumper(cp_list[i]->tag);
		if (!dump_constant_info) {
			LOGE("%s:%d, Could not get constant dumper\n", __FUNCTION__, __LINE__);
			return;
		}
		dump_constant_info(cp_list[i]);
	}

	LOG_LEAVE();
}
